We will try creating the sub component and connect the information between it and its parent component.
Extending the previous example, we want to show the customer's details inside the customer.index.component.html
So here what we are going to do
Customer class
for strong typed programmingdetail component
export class Customer {
Id: number;
Name: string;
Phone: string;
Age: number;
}
Because the class is not under the same path of customer components,
we have to load it thru Systemjs
. Open /app/systemjs.config.js
and add the new settings for all the class within /app/class
System.config({
transpiler: 'typescript',
typescriptOptions: { emitDecoratorMetadata: true },
map: {
//Skip…
'class': 'app/class'
},
paths: {
//Skip…
"class:*": "app/class/*.js"
},
packages: {
//Skip…
'class': { main: '*.js', defaultExtension: 'js' }
}
});
We are going to make a child component
: Customer detail, for main component
: Cusomter index.
When clicking on the customer's name, hide the customers' list and display the content of child component
.
import {Component, OnInit, Input} from '@angular/core';
import {Customer} from '../../class/Customer';
@Component({
selector: 'customer-detail',
templateUrl: '/app/Basic/Customer/customer-detail.component.html',
styleUrls: ['/app/Basic/Customer/customer-detail.component.css']
})
export class CustomerDetailComponent implements OnInit {
@Input('selectedCustomer') customer: Customer;
constructor() {
}
ngOnInit() {
}
}
Notice that we have an INPUT VARIABLE: customer
, alias name: selectedCustomer
, in the child component.
Then import it to customer.app.module
.
//Skip...
import {CustomerDetailComponent} from './customer-detail.component';
@NgModule({
//Skip...
declarations: [CustomerAppComponent, CustomerIndexComponent, CustomerCreateComponent, CustomerEditComponent, CustomerDetailComponent],
})
//...
<div [hidden]="selectedCustomer">
<div>
<input type="button" class="btn btn-primary" value="Create New" (click)="goToCreate()" />
</div>
<br />
<div>
<table class="table table-bordered table-hover">
<thead><tr>
<td>ID</td>
<td class="text-center">Name</td>
<td class="text-center">Phone</td>
<td class="text-center">Age</td>
<td></td>
</tr></thead>
<tbody>
<tr *ngFor="let item of customers; let sn=index">
<td class="col-sm-1 text-center">{{item.Id}}</td>
<td class="col-sm-2">
<a [innerHtml]="item.Name" (click)="showDetail(item)"></a>
</td>
<td class="col-sm-2">{{item.Phone}}</td>
<td class="col-sm-1">{{item.Age}}</td>
<td class="col-sm-2"><input type="button" class="btn btn-info" value="Edit" (click)="editCustomer(item)" /> <input type="button" class="btn btn-danger" value="Delete" (click)="deleteCustomer(item)" /></td>
</tr></tbody></table>
</div>
</div>
<div *ngIf="selectedCustomer">
<customer-detail [selectedCustomer]="selectedCustomer" >
</customer-detail>
<input type="button" class="btn btn-primary" value="Back" (click)="backToList()" />
</div>
Because we have an input variable in Detail component
, we need to set a Property binding: [selectedCustomer]= "selectedCustomer"
for it.
PS. The latter selectedCustomer
is the object in parent component.
Also we add the events for the parent component.
export class CustomerIndexComponent implements OnInit {
title: string;
customers: Customer[];
selectedCustomer: Customer;
constructor(private router:Router) {
this.title = "Customers:Index";
this.customers = CUSTOMERS;
}
//Go to create page
private goToCreate() {
this.router.navigate(['Basic/Customer/Create']);
}
//Back to list (Show list)
private backToList() {
this.selectedCustomer = null;
}
//Show details of the customer
private showDetail(cust: Customer) {
this.selectedCustomer = cust;
}
const CUSTOMERS: Customer[] = …
}
We can send something from sub-component to parent with EventEmitter.
We will
SysEvent
SysEvent
, from sub-component to parent component. That means form customer-detail.component
to customer-index.component
.export class SysEvent {
Title: string;
Msg: string;
CreateBy: string;
CreateOn: Date = new Date();
constructor(fields?: {
Title?: string,
Msg?: string,
CreateBy?: string,
CreateOn?: Date;
}) {
if (fields) Object.assign(this, fields);
}
}
In the class's constructor, use
Object.assign
to create the initial properties values
Create an EventEmitter in Customer Detail component and the call-back function in Customer Index component.
import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
import {SysEvent} from '../../class/SysEvent';
@Component({
selector: 'customer-detail',
templateUrl: '/app/Basic/Customer/customer-detail.component.html'
})
export class CustomerDetailComponent implements OnInit {
@Input('selectedCustomer') customer: Customer;
@Output('emit-events') emitEvents = new EventEmitter<SysEvent[]>(true); //Must set the EventEmitter to async
ngOnInit() {
//Emit event
let evts: SysEvent[] = [
new SysEvent({
Title: "Info",
Msg: "You are looking at " + this.customer.Name + "'s information.",
CreateBy: "angular",
CreateOn: new Date()
})
];
this.emitEvents.emit(evts);
}
}
Notice that we have to initialize the
emitEvents
in async mode.
If not, we will get the following message which angular is saying that it cannot get the correct value from the child component.
core.umd.min.js:28 EXCEPTION: Error in /app/Basic/Customer/customer-index.component.html:48:5 caused by: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'.
Set a call-back for the EventEmitter.
events:SysEvent[]
private setSysEvents(data: SysEvent[]) {
this.events = data;
}
<!-- Skip ... -->
<div *ngIf="selectedCustomer">
<customer-detail [selectedCustomer]="selectedCustomer" (emit-events)="setSysEvents($event)">
</customer-detail>
<input type="button" class="btn btn-primary" value="Back" (click)="backToList()" />
</div>
<!-- New html -->
<hr />
<div *ngIf="events">
<div class="list-group" *ngFor="let evn of events; let sn=index">
<a href="#" class="disabled list-group-item" [innerHtml]="evn.Msg"></a>
</div>
</div>
In the next day-sharing, we will use angular's pipes and our custom pipe to provide a more clean message from the Customer Detail Component.